#ifndef CO_H
#define CO_H
/*
 *  Copyright 2021-2025 Michael T. Richter as Half-Baked Software
 *  This program is free software. It comes without any warranty, to the extent
 *  permitted by applicable law. You can redistribute it and/or modify it under
 *  the terms of the Do What The Fuck You Want To Public License, Version 2, as
 *  published by Sam Hocevar. See the file COPYING or http://www.wtfpl.net/ for
 *  more details.
 */
/** @file
 *  @brief CPU library CO component, user-facing API
 *
 *  CO is a small, simple, and fast implementation of Lua-style coroutines.  It
 *  has an API of four public functions (plus a fifth alias for clarity of
 *  communicating code purpose):
 *  - co_create();
 *  - co_destroy();
 *  - co_resume();
 *  - co_yield();
 *  - co_start();
 *
 *  Note that co_start is just a synonym for co_resume.
 *
 *  Coroutine functions can be called as regular functions as well as via
 *  Start/Resume.  Yielding during a normal function context is a null operation
 *  thus treating the coroutine function as if it were a normal function (at a
 *  miniscule cost in overhead).
 *
 *  This coroutine implementation is a strict parent/child variety.  Yielding
 *  within a coroutine always yields control to the routine that started/resumed
 *  it.  The parameter passed to co_yield() becomes the return value of
 *  the co_resume() call that started the child, and the data element passed
 *  to co_resume() becomes the return value of CPO_co_yield().  This permits
 *  directed communication between parent and child.
 */

// Required headers.
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

typedef uintptr_t stack_t;

typedef struct _co_t *co_t;             ///< An opaque type capturing the vital state of a coroutine in operation.
typedef void (*co_function)(void *);    ///< The type of the function that a  coroutine must conform to.

/*! Create a coroutine from a function and a provided stack area.
 *
 *  @param[in] co_cb        The function implementing the coroutine's operation.
 *  @param[in] stack_count  Number of items (NOT BYTES!) the stack can hold.
 *  @param[in] stack_store  NULL for dynamic, or pointer to properly-sized array.
 *  @result The coroutine object used in subsequent operations or NULL on
 *          failure.
 */
co_t co_create(co_function co_cb, size_t stack_count, stack_t *stack_store, void *stash);

/*! Extract the user-stashed data from the coroutine handle.
 *
 *  The coroutine creation has an optional user stash value that can be
 *  associated with it.  This permits other routines that know how to use it to
 *  extract it.  A possible use case for this is to store process information
 *  like names, priority, etc.  A scheduler could use the priority element to
 *  insert the coroutine into a heap and pull the top priority thread each time
 *  it schedules, for example, or a status routine could print an associated
 *  task name.
 *
 *  @result Whatever was passed into the `co_create()` call that made the `co_t`.
 */
void *co_get_stash(co_t co);

/*! Destroy a previously-created coroutine.
 *
 *  There is a small amount of dynamic data used in making a coroutine of
 *  approximately 8 bytes.  In cases where coroutines run to completion and exit
 *  it is necessary to call co_destroy() to reclaim this data.
 *
 *  @param[in] co   A pointer to the coroutine object being cleaned up.
 *
 *  @note   The co_t object handle will be set to NULL to prevent its further
 *          use.
 */
void co_destroy(co_t *co);

/*! Transfer control to a coroutine.
 *
 *  Either starts an inactive coroutine or continues from the last point of
 *  calling co_yield() inside of it.
 *
 *  @param[in] co   The coroutine being started/continued.
 *  @param[in] data Whatever is passed here is what is RETURNED from
 *                  co_yield().
 *  @result Whatever is passed to co_yield() is RETURNED here, NULL
 *          otherwise.
 *
 *  @note   The function co_start() is a synonym for this function used
 *          strictly to communicate intent.  There is no other differentiation.
 *          In general co_start() ignores its return value since it signals a
 *          coroutine that requires initialization.  co_resume() is used in the
 *          actual operation of the coroutine and will have its return value
 *          inspected most times.
 */
void *co_resume(co_t co, void *data);

/*! Yield control to the parent.
 *
 *  If running as a coroutine (the usual case) yields control to the parent
 *  thread of control.  If not running as a coroutine acts as a null operation.
 *
 *  @param[in]  data    Data passed in this parameter becomes the return value
 *                      of the co_resume() call.
 *  @result Data passed in to co_resume() becomes the return value of this
 *          function.
 */
void *co_yield(void *data);

/*! Transfer control to a coroutine.
 *
 *  @note   The function co_start() is a synonym for co_resume() given a
 *          different name strictly to communicate intent.  There is no other
 *          difference.  In general co_start() ignores its return value since
 *          it signals a coroutine that requires initialization.  co_resume()
 *          is used in the actual operation of the coroutine and will have its
 *          return value inspected most times.
 */
extern void *(*co_start)(co_t, void *);

#endif //CO_H
